#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "gdst_array.h"

typedef struct array_t
{
	void *data;
	size_t elsz;
	size_t size;
	size_t used;
	size_t count;
}array_t;

array_p gdst_arraynew(size_t size, size_t elsize)
{
	array_p obj;
	obj = (array_p)malloc(sizeof(array_t));
	if (!obj)
		return NULL;
	obj->data = malloc(size * elsize);
	if (!obj->data)
	{
		free(obj);
		return NULL;
	}
	obj->elsz = elsize;
	obj->size = elsize * size;
	obj->used = 0;
	obj->count = 0;
	return obj;
}

void gdst_arrayfree(array_p *self_p)
{
	assert(self_p);
	if (*self_p)
	{
		array_p self = *self_p;
		free(self->data);
		free(self);
		*self_p = NULL;
	}
}

int gdst_arraypush(array_p self, void *el)
{
	void *where;
	assert(self);
	assert(el);

	if(self->used == self->size)
	{
		self->size *= 2;
		self->data = realloc(self->data, self->size * self->elsz);
		assert(self->data);
		if (!self->data)
			return 1;
	}
	where = (char *)self->data + self->used;
	memcpy(where, el, self->elsz);
	self->used += self->elsz;
	self->count++;
	return 0;
}

int gdst_arrayputat(array_p self, void *el, size_t index)
{
	void *from, *to;
	size_t count;
	assert(self);
	assert(el);
	assert(index <= self->count);

	if((self->used + self->elsz) == self->size)
	{
		self->size *= 2;
		self->data = realloc(self->data, self->size * self->elsz);
		assert(self->data);
		if (!self->data)
			return 1;
	}
	/*grow array if necessary and move data right from index one position (make room)*/
	to = (char *)self->data + (index * self->elsz) + self->elsz;
	from = (char *)self->data + (index * self->elsz);
	count = self->used - (index * self->elsz);
	memmove(to, from, count); 
	memcpy(from, el, self->elsz);
	self->used += self->elsz;
	self->count++;
	return 0;
}

int gdst_arrayremat(array_p self, size_t index)
{
	void *from, *to;
	size_t count;
	assert(self);
	assert(index <= self->count);

	from = (char *)self->data + ((index + 1) * self->elsz);
	to = (char *)self->data + (index * self->elsz);
	count = self->used - ((index + 1) * self->elsz);
	memmove(to, from, count); 
	self->used -= self->elsz;
	self->count--;
	return 0;
}

/*
	remove last element
	don't forget to free the return
*/
void* gdst_arraypop(array_p self)
{
	void *item;
	assert(self);
	item = malloc(self->elsz);
	memcpy(item,(char *)self->data + (self->count * self->elsz), self->elsz);
	self->size -= self->elsz;
	self->count--;
	return item;
}

void* gdst_arraygetat(array_p self, size_t index)
{
	void *from;
	assert(self);
	assert(index <= self->count);
	from = (char *)self->data + (index * self->elsz);
	return from;
}

size_t gdst_arraycount(array_p self)
{
	assert(self);
	return self->count;
}
